## Test First, by Intention

### A code and culture translation from the original Smalltalk to Ruby

Original by Ronald Jeffries, translation by Aleksi Niemela and Dave Thomas

In this document we show you the Ruby version of the Smalltalk code published in the Pink book. There's also an online version of the original (PDF 0.5MB, and zipped 1.0MB).

## Table of contents

### 1. Introduction

### 2. In the Beginning a Test Should Fail

### 3. Then it should pass

### 4. The second test

### 5. Coming up with an Algorithm

### 6. Refactoring

### 7. Refactoring II

## 1. Introduction

Chapter 14 of XP Installed is a blow-by-blow account of Chet and Ron developing a simple Smalltalk program using test-first design.

There has been some discussion that the use of Smalltalk is confusing for some readers, and people have started offering their translations in various languages. Here's a possible Ruby version.

We had a couple of options when performing this translation. We could have written the code using more of Ruby's features, which would have resulted in a more compact program. However, it would then have been difficult to relate this Ruby code back to the Smalltalk original. We therefore took the path of translating the Smalltalk directly into Ruby, a fairly straightforward job as Ruby inherits many of Smalltalk's strengths.

In the descriptions that follow, page numbers reference the corresponding pages in XP Installed, and the numbers in parenthesis are for the online version, which has different numbering.

The code examples for each step in this proces are in two columns. On the left we have the code we start to develop from, and on the right is the code after modification. The part of code which changes or is being added is marked with darker color. The application code is colored in shades of Ruby, and the test code is in green (symbolizing the go color of traffic lights).

## 2. In the Beginning a Test Should Fail

### Page 109 (130)

We `know' we need a class to represent a sum. Ron says ``In Smalltalk, you don't have to define the types of the variables.'' In Ruby, you don't even have to define the variables--they spring into existence when you use them. So, the Ruby code corresponding to the original Smalltalk is pretty simple--a plain class definition.

    class Sum
    end

### Page 110 (131)

We'll add initialization code, so that the constructor will set the object's attributes. From within the class we can access the attributes in two ways, directly as instance variables (these start with an 'at' sign) or via the accessor functions: ```self.name = name`'' or ```@name = name`''.

The former is more like the Smalltalk code, but the latter is more idiomatic in Ruby, so we'll use it. We'll also use parallel assignment, as it shortens the psychological distance between the variables and the parameter list.

    class Sum
    end
    class Sum
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

#### (132)

Then, following the order of the original text, we introduce accessors. Instead of writing the code ourselves (although it's very simple) we reduce the workload and the opportunity for errors by letting Ruby write them for us.

    class Sum
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

#### (133/1)

Now it's time to write the first test. Ron & Chet actually came up with single line creating an emptySummarizer first, but we add the assert code right away.

      def testEmpty
        summarizer = emptySummarizer
      end
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end

#### (133/9)

In RubyUnit it's a common idiom to store the test suite in separate file than the class under test. It's also common to add a couple of lines of driver code to make the test directly executable from the command line. With this code at the bottom of a test file, you can run the tests in the file by executing the file. You can also include the file in a larger body of tests, in which case the driver code would be ignored.

In any event the test case needs a TestCase wrapper.

      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### First test run

Then we run the test. And it fails.

     TestSummarizer#testEmpty test_summarizer.rb:6:in `testEmpty': undefined local
    variable or method `emptySummarizer' for #<TestSummarizer:0x4017c448>
    (NameError)
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:13

## 3. Then it should pass

#### (134/1)

Create the basic `Summarizer` class with two collections:

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end
    class Summarizer
    end

#### (134/2)

So now we can create the `emptySummarizer` method in `TestSummarizer`.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### Second test run (134/1)

And run the tests again:

    TestSummarizer#testEmpty E.
    Time: 0.0012
    FAILURES!!!
    Test Results:
     Run: 1/1(0 asserts) Failures: 0 Errors: 1
    Errors: 1
     test_summarizer.rb:10:in `initialize': wrong # of arguments(2 for 0)
    (ArgumentError)
    	from test_summarizer.rb:10:in `new'
    	from test_summarizer.rb:10:in `emptySummarizer'
    	from test_summarizer.rb:6:in `testEmpty'
    	from test_summarizer.rb:16

#### (135/1)

The test output indicates we're calling `Summarizer`'s constructor with too many parameters. Well, that could well be the case as we haven't written the constructor yet. Let's create the constructor, and at the same time add the accessors.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
    end

#### New test run

     TestSummarizer#testEmpty test_summarizer.rb:7:in `testEmpty': undefined method
    `summary' for #<Summarizer:0x4017bcb4> (NameError)
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:16

#### (135/2)

It seems that `emptySummarizer` managed to return the right object, but the last run complained about a method that didn't exist. It's time to introduce it.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
      def summary
        exit
      end
    end

#### (135/3)

Let's go with Chet and return the concatenation of the two collections.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
      def summary
        exit
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
      def summary
        @first.concat @second
      end
    end

What do our test say?

    TestSummarizer#testEmpty .
    Time: 0.000496
    OK (1/1 tests  1 asserts)

Heh, the test passes. Ship it. ;-)

## 4. The second test

#### (136/1)

Let's add another test, based on Ron's table.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        assert_equals(3, summarizer.summary.size)
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### (137/1)

We need to add the factory method `abcSummarizer`. We'll do the simplest thing for now, and define it better later.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        assert_equals(3, summarizer.summary.size)
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        assert_equals(3, summarizer.summary.size)
      end
      def abcSummarizer
        Summarizer.new([],[])
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### (137/2)

We're on a roll! Let's keep the momentum going by adding all the rest of the tests. We know what to expect from the summary so it's all about stating it in code.

Ron & Chet have a slightly strange way of accessing the container from the head, the end, and by indexing it in the middle. Let's follow it. We also create a pronoun variable ```summary`'' because we're referring to it a lot.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        assert_equals(3, summarizer.summary.size)
      end
      def abcSummarizer
        Summarizer.new([],[])
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert(summary.size == 3)
        assert(summary.first.name == 'a')
        assert(summary.first.amount == 11)
        assert(summary[1].name == 'c')
        assert(summary[1].amount == 2)
        assert(summary.last.name == 'b')
        assert(summary.last.amount == 3)
      end
      def abcSummarizer
        Summarizer.new([],[])
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### A Bonus Test Run (not available in stores)

Actually there was no test run at this point in the book, but let's add one, so we know where we are before continuing. Note that here we follow the original way of asserting by saying ```real_value == expected`'' We're going to change that later.

    TestSummarizer#testABC F.
    TestSummarizer#testEmpty .
    Time: 0.001526
    FAILURES!!!
    Test Results:
     Run: 2/2(2 asserts) Failures: 1 Errors: 0
    Failures: 1
     test_summarizer.rb:15:in `testABC': The condition is <false:FalseClass>
    (RUNIT::AssertionFailedError)
    	from test_summarizer.rb:30

#### (137/3)

The test fails as the size of summary (empty container) can't possibly be 3 as expected. Now it's time to redefine `abcSummarizer`. The collections get built in their own methods.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert(summary.size == 3)
        assert(summary.first.name == 'a')
        assert(summary.first.amount == 11)
        assert(summary[1].name == 'c')
        assert(summary[1].amount == 2)
        assert(summary.last.name == 'b')
        assert(summary.last.amount == 3)
      end
      def abcSummarizer
        Summarizer.new([],[])
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert(summary.size == 3)
        assert(summary.first.name == 'a')
        assert(summary.first.amount == 11)
        assert(summary[1].name == 'c')
        assert(summary[1].amount == 2)
        assert(summary.last.name == 'b')
        assert(summary.last.amount == 3)
      end
      def abcSummarizer
        Summarizer.new(acCollection,abCollection)
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### (138/1)

Add the collection builders. Here we just return an array (the simplest container) populated with proper `Sum` objects.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert(summary.size == 3)
        assert(summary.first.name == 'a')
        assert(summary.first.amount == 11)
        assert(summary[1].name == 'c')
        assert(summary[1].amount == 2)
        assert(summary.last.name == 'b')
        assert(summary.last.amount == 3)
      end
      def abcSummarizer
        Summarizer.new(acCollection,abCollection)
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert(summary.size == 3)
        assert(summary.first.name == 'a')
        assert(summary.first.amount == 11)
        assert(summary[1].name == 'c')
        assert(summary[1].amount == 2)
        assert(summary.last.name == 'b')
        assert(summary.last.amount == 3)
      end
      def abcSummarizer
        Summarizer.new(acCollection,abCollection)
      end
      def acCollection
        [Sum.new('a', 1), Sum.new('c', 2)]
      end
      def abCollection
        [Sum.new('a', 10), Sum.new('b', 3)]
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

Finally it's time to start testing with our enhanced testing suite.

    TestSummarizer#testABC F.
    TestSummarizer#testEmpty .
    Time: 0.001606
    FAILURES!!!
    Test Results:
     Run: 2/2(2 asserts) Failures: 1 Errors: 0
    Failures: 1
     test_summarizer.rb:15:in `testABC': The condition is <false:FalseClass>
    (RUNIT::AssertionFailedError)
    	from test_summarizer.rb:36

The failure is coming from the assertion that `summary.size == 3`. If we had written the assertions in more Rubyistic way, we would have said `assert_equals(3, summary.size)` and the error message would have been clearer saying something like "expected: <3> but was: <4>".

But back to the plot. Clearly our algorithm for summarizing is not working right. That's no suprise as we don't yet have any real algorithm...

## 5. Coming Up With an Algorithm

#### (139/1)

Time to write the start of a summary method. Here we make our first change from Chet and Ron's code.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
      def summary
        @first.concat @second
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
      def summary
        processFirst
        processSecond
      end
    end

#### (139/2)

Add an instance variable to contain the summary we're producing.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
      end
      def summary
        processFirst
        processSecond
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        processFirst
        processSecond
      end
    end

#### (139/3)

Next they noticed the two different `processes` were actually same thing. The code takes shape nicely as the name morphs to a passed parameter.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        processFirst
        processSecond
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
    end

#### (139/4)

Describing the `process` in terms of the collection is easy: process each item of the collection. This uses a Ruby iterator. The code in the braces will be executed once for each member of the collection, passing in successive members.

The next deviation from the original is the naming of the iteration variable. In Ruby, ```each`'' is the common name for an iterator method, and we'll pass in each element to a variable called ```item`.''

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
    end

Let's see how far we can get with this code.

     TestSummarizer#testABC ./summarizer.rb:19:in `process': undefined method
    `processItem' for #<Summarizer:0x4017ab5c> (NameError)
    	from ./summarizer.rb:19:in `each'
    	from ./summarizer.rb:19:in `process'
    	from ./summarizer.rb:15:in `summary'
    	from test_summarizer.rb:14:in `testABC'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:36

#### (140/1)

The test whines on about the missing method `processItem`. Time to define it: no one likes a whining test.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
      end
    end

#### (140/2)

Still Chet and Ron don't exactly know how to do it. So they decide to find a matching sum for current sum, and add to it.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
    end

Time to test and see what's needed.

     TestSummarizer#testABC ./summarizer.rb:22:in `processItem': undefined method
    `matchingSum' for #<Summarizer:0x4017a9f4> (NameError)
    	from ./summarizer.rb:19:in `process'
    	from ./summarizer.rb:19:in `each'
    	from ./summarizer.rb:19:in `process'
    	from ./summarizer.rb:15:in `summary'
    	from test_summarizer.rb:14:in `testABC'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:36

#### (141/1)

The test forces us to write `matchingSum`. We'll write it using Chet's Smalltalk style. ```detect`'' is another iterator method: it passes successive values in a collection to the block, stopping when the block returns true. If the block never matches, then detect returns `nil`. In non-Smalltalk terms, `detect` tries to find an item from the collection which satisfies the test passed as a block. It returns the element it finds or `nil` if none was found.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

Now we're pretty hopeful, as it looks like we've done all the work. So let's test it.

     TestSummarizer#testABC ./summarizer.rb:22:in `processItem': undefined method
    `add' for #<Sum:0x4017a328> (NameError)
    	from ./summarizer.rb:19:in `process'
    	from ./summarizer.rb:19:in `each'
    	from ./summarizer.rb:19:in `process'
    	from ./summarizer.rb:15:in `summary'
    	from test_summarizer.rb:14:in `testABC'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:36

But, no, it's not working. We're missing the add method used in processItem.

#### (141/2)

Ruby allows more concise expression for `@amount = @amount + sum.amount`, so we'll use it.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

Now, we're finally ready. Aren't we? Well, the test will tell.

    TestSummarizer#testABC F.
    TestSummarizer#testEmpty .
    Time: 0.00183
    FAILURES!!!
    Test Results:
     Run: 2/2(2 asserts) Failures: 1 Errors: 0
    Failures: 1
     test_summarizer.rb:15:in `testABC': The condition is <false:FalseClass>
    (RUNIT::AssertionFailedError)
    	from test_summarizer.rb:36

#### (141/3)

By debugging the code we saw that we're not returning the right collection from `Summarizer#summary`. So let's fix it by returning the collection instance variable that `summary` has been referring all the time.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
        @summary
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

New try:

    TestSummarizer#testABC .
    TestSummarizer#testEmpty .
    Time: 0.001328
    OK (2/2 tests  8 asserts)

And the code passes our test. Everything's perfect. Let's go home!

Except that we might like to continue to refactor our code into better shape.

## 6. Refactoring

#### (142/1)

Chet and Ron decide to change non-communicative names into better ones. (We think that ``Chet'' and ``Ron'' are OK, but it's their call...)

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        process(@first)
        process(@second)
        @summary
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

Then testing tells us if we've forgotten something.

     TestSummarizer#testABC ./summarizer.rb:18:in `summary': undefined method
    `summarize' for #<Summarizer:0x4017a3c8> (NameError)
    	from test_summarizer.rb:14:in `testABC'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:36

#### (142/2)

And we have. So let's change the method definition too.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def process(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def summarize(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

Tests never lie....

    TestSummarizer#testABC .
    TestSummarizer#testEmpty .
    Time: 0.001253
    OK (2/2 tests  8 asserts)

Another happy moment.

#### (142/3)

But let's not stop. There's still that vague word `process' in our code.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def summarize(collection)
        collection.each {|item| processItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def summarize(collection)
        collection.each {|item| summarizeItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

     TestSummarizer#testABC ./summarizer.rb:23:in `summarize': undefined method
    `summarizeItem' for #<Summarizer:0x4017a3c8> (NameError)
    	from ./summarizer.rb:23:in `each'
    	from ./summarizer.rb:23:in `summarize'
    	from ./summarizer.rb:18:in `summary'
    	from test_summarizer.rb:14:in `testABC'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `send'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:35:in `run_bare'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testresult.rb:75:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testcase.rb:29:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:15:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `each'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/testsuite.rb:14:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:27:in `run'
    	from /tc/usr/lib/ruby/site_ruby/1.6/runit/cui/testrunner.rb:20:in `run'
    	from test_summarizer.rb:36

#### (142/4)

And the fix.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def summarize(collection)
        collection.each {|item| summarizeItem(item) }
      end
      def processItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def summarize(collection)
        collection.each {|item| summarizeItem(item) }
      end
      def summarizeItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end

Re-test.

    TestSummarizer#testABC .
    TestSummarizer#testEmpty .
    Time: 0.001269
    OK (2/2 tests  8 asserts)

Now everything works, and this is where the book stops. However, there's still a tad more refactoring to do.

## 7. Refactoring II

First, tidy the application code:

1. Rename `@summary` to `@summaryResult` so that it won't get confused with the method `Summarizer#summary`.
2. Rename method `summarize`'s parameter `sums`, as the collection is really collection of sums. Also the iterator variable `item` gets renamed to `sum`, as each of collection `sums` elements is really a `sum` :).
3. The ugly ```if !match`'' get transformed to ```if not match`'', and then quickly to ```unless match`''.

    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summary = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summary
      end
      def summarize(collection)
        collection.each {|item| summarizeItem(item) }
      end
      def summarizeItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summary.detect do
          |item| item.name == sum.name
        end
        if !match
          match = Sum.new(sum.name, 0)
          @summary << match
        end
        match
      end
    end
    class Sum
      attr_accessor :name, :amount
      def initialize(name, amount)
        @name, @amount = name, amount
      end
      def add(sum)
        @amount += sum.amount
      end
    end

    class Summarizer
      attr_accessor :first, :second
      def initialize(first, second)
        @first, @second = first, second
        @summaryResult = []
      end
      def summary
        summarize(@first)
        summarize(@second)
        @summaryResult
      end
      def summarize(sums)
        sums.each {|sum| summarizeItem(sum) }
      end
      def summarizeItem(sum)
        matchingSum(sum).add(sum)
      end
      def matchingSum(sum)
        match = @summaryResult.detect do
          |item| item.name == sum.name
        end
        unless match
          match = Sum.new(sum.name, 0)
          @summaryResult << match
        end
        match
      end
    end

Tidy the tests, too:

1. We can use ```assert_equal(1, foo)`'' instead of ```assert(foo == 1)`'', as it provides much better error messages.
2. We can index the collections better. In Ruby, a negative index counts from the end of a collection, so we can use -1. At this point the code should be in good-enough shape for the assertions to be stuffed inside a loop should we add more cases. It's a good idea to make larger blocks of repeating code table driven, but here we're just too lazy.

    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert(summary.size == 3)
        assert(summary.first.name == 'a')
        assert(summary.first.amount == 11)
        assert(summary[1].name == 'c')
        assert(summary[1].amount == 2)
        assert(summary.last.name == 'b')
        assert(summary.last.amount == 3)
      end
      def abcSummarizer
        Summarizer.new(acCollection,abCollection)
      end
      def acCollection
        [Sum.new('a', 1), Sum.new('c', 2)]
      end
      def abCollection
        [Sum.new('a', 10), Sum.new('b', 3)]
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end
    require 'runit/testcase'
    require 'summarizer'

    class TestSummarizer < RUNIT::TestCase
      def testEmpty
        summarizer = emptySummarizer
        assert( summarizer.summary.empty? )
      end
      def emptySummarizer
        Summarizer.new([],[])
      end
      def testABC
        summarizer = abcSummarizer
        summary = summarizer.summary
        assert_equals(3, summary.size)
        assert_equals('a', summary[0].name)
        assert_equals( 11, summary[0].amount)
        assert_equals('c', summary[1].name)
        assert_equals(  2, summary[1].amount)
        assert_equals('b', summary[-1].name)
        assert_equals(  3, summary[-1].amount)
      end
      def abcSummarizer
        Summarizer.new(acCollection,abCollection)
      end
      def acCollection
        [Sum.new('a', 1), Sum.new('c', 2)]
      end
      def abCollection
        [Sum.new('a', 10), Sum.new('b', 3)]
      end
    end

    if $0 == __FILE__
      require 'runit/cui/testrunner'
      RUNIT::CUI::TestRunner.run(TestSummarizer.suite)
    end

#### The last check

    TestSummarizer#testABC .
    TestSummarizer#testEmpty .
    Time: 0.001288
    OK (2/2 tests  8 asserts)

Ok, that's that. Thank you for wandering through this quite long presentation. There wasn't so much to read, but the repeating code made it surprisingly long. The moral of the story? Well, at least you shouldn't repeat code like we did... :)

Dave & Aleksi